/****************************************************************************
 * fs/procfs/fs_procfsproc.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <malloc.h>
#include <execinfo.h>

#ifdef CONFIG_SCHED_CRITMONITOR
#  include <time.h>
#endif

#include <nuttx/arch.h>
#include <nuttx/nuttx.h>
#include <nuttx/irq.h>
#include <nuttx/tls.h>
#include <nuttx/sched.h>
#include <nuttx/kmalloc.h>
#include <nuttx/environ.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mm/mm.h>
#include <nuttx/queue.h>
#include <nuttx/lib/lib.h>

#include "fs_heap.h"

#if !defined(CONFIG_SCHED_CPULOAD_NONE) || defined(CONFIG_SCHED_CRITMONITOR)
#  include <nuttx/clock.h>
#endif

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define STATUS_LINELEN PATH_MAX

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This enumeration identifies all of the task/thread nodes that can be
 * accessed via the procfs file system.
 */

enum proc_node_e
{
  PROC_LEVEL0 = 0,                    /* The top-level directory */
  PROC_STATUS,                        /* Task/thread status */
  PROC_CMDLINE,                       /* Task command line */
#ifndef CONFIG_SCHED_CPULOAD_NONE
  PROC_LOADAVG,                       /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  PROC_CRITMON,                       /* Critical section monitor */
#endif
#if CONFIG_MM_BACKTRACE >= 0
  PROC_HEAP,                          /* Task heap info */
#endif
#ifdef CONFIG_DEBUG_MM
  PROC_HEAP_CHECK,                    /* Task heap check flag */
#endif
  PROC_STACK,                         /* Task stack info */
  PROC_GROUP,                         /* Group directory */
  PROC_GROUP_STATUS,                  /* Task group status */
  PROC_GROUP_FD                       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , PROC_GROUP_ENV                    /* Group environment variables */
#endif
};

/* This structure associates a relative path name with an node in the task
 * procfs
 */

struct proc_node_s
{
  FAR const char *relpath;            /* Relative path to the node */
  FAR const char *name;               /* Terminal node segment name */
  uint8_t node;                       /* Type of node (see enum proc_node_e) */
  uint8_t dtype;                      /* dirent type (see include/dirent.h) */
};

/* This structure describes one open "file" */

struct proc_file_s
{
  struct procfs_file_s base;          /* Base open file structure */
  FAR const struct proc_node_s *node; /* Describes the file node */
  pid_t pid;                          /* Task/thread ID */
  char line[STATUS_LINELEN];          /* Pre-allocated buffer for formatted lines */
};

/* This structure describes one open "directory" */

struct proc_dir_s
{
  struct procfs_dir_priv_s base;      /* Base directory private data */
  FAR const struct proc_node_s *node; /* Directory node description */
  pid_t pid;                          /* ID of task/thread for attributes */
};

/* This structure used with the env_foreach() callback */

struct proc_envinfo_s
{
  FAR struct proc_file_s *procfile;
  FAR char *buffer;
  FAR off_t offset;
  size_t buflen;
  size_t remaining;
  size_t totalsize;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static FAR const char * const g_policy[4] =
{
  "SCHED_FIFO", "SCHED_RR", "SCHED_SPORADIC"
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Helpers */

static FAR const struct proc_node_s *
               proc_findnode(FAR const char *relpath);
static ssize_t proc_status(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#ifndef CONFIG_SCHED_CPULOAD_NONE
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
static ssize_t proc_critmon(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif
#if CONFIG_MM_BACKTRACE >= 0
static ssize_t proc_heap(FAR struct proc_file_s *procfile,
                         FAR struct tcb_s *tcb, FAR char *buffer,
                         size_t buflen, off_t offset);
#endif
#ifdef CONFIG_DEBUG_MM
static ssize_t proc_heapcheck(FAR struct proc_file_s *procfile,
                         FAR struct tcb_s *tcb, FAR char *buffer,
                         size_t buflen, off_t offset);
static ssize_t proc_heapcheck_write(FAR struct proc_file_s *procfile,
                         FAR struct tcb_s *tcb, FAR const char *buffer,
                         size_t buflen, off_t offset);
#endif
static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static int     proc_groupenv_callback(FAR void *arg, FAR const char *pair);
static ssize_t proc_groupenv(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif

/* File system methods */

static int     proc_open(FAR struct file *filep, FAR const char *relpath,
                 int oflags, mode_t mode);
static int     proc_close(FAR struct file *filep);
static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t proc_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static int     proc_dup(FAR const struct file *oldp,
                 FAR struct file *newp);

static int     proc_opendir(const char *relpath,
                 FAR struct fs_dirent_s **dir);
static int     proc_closedir(FAR struct fs_dirent_s *dir);
static int     proc_readdir(FAR struct fs_dirent_s *dir,
                            FAR struct dirent *entry);
static int     proc_rewinddir(FAR struct fs_dirent_s *dir);

static int     proc_stat(FAR const char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly externed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct procfs_operations g_proc_operations =
{
  proc_open,          /* open */
  proc_close,         /* close */
  proc_read,          /* read */
  proc_write,         /* write */
  NULL,               /* poll */

  proc_dup,           /* dup */

  proc_opendir,       /* opendir */
  proc_closedir,      /* closedir */
  proc_readdir,       /* readdir */
  proc_rewinddir,     /* rewinddir */

  proc_stat           /* stat */
};

/* These structures provide information about every node */

static const struct proc_node_s g_level0node =
{
  "",             "",        (uint8_t)PROC_LEVEL0,       DTYPE_DIRECTORY   /* Top-level directory */
};

static const struct proc_node_s g_status =
{
  "status",       "status",  (uint8_t)PROC_STATUS,       DTYPE_FILE        /* Task/thread status */
};

static const struct proc_node_s g_cmdline =
{
  "cmdline",      "cmdline", (uint8_t)PROC_CMDLINE,      DTYPE_FILE        /* Task command line */
};

#ifndef CONFIG_SCHED_CPULOAD_NONE
static const struct proc_node_s g_loadavg =
{
  "loadavg",       "loadavg", (uint8_t)PROC_LOADAVG,     DTYPE_FILE        /* Average CPU utilization */
};
#endif

#ifdef CONFIG_SCHED_CRITMONITOR
static const struct proc_node_s g_critmon =
{
  "critmon",       "critmon", (uint8_t)PROC_CRITMON,     DTYPE_FILE        /* Critical Section Monitor */
};
#endif

#if CONFIG_MM_BACKTRACE >= 0
static const struct proc_node_s g_heap =
{
  "heap",         "heap",   (uint8_t)PROC_HEAP,          DTYPE_FILE        /* Task heap info */
};
#endif

#ifdef CONFIG_DEBUG_MM
static const struct proc_node_s g_heapcheck =
{
  "heapcheck",    "heapcheck", (uint8_t)PROC_HEAP_CHECK, DTYPE_FILE        /* Task heap info */
};
#endif

static const struct proc_node_s g_stack =
{
  "stack",        "stack",   (uint8_t)PROC_STACK,        DTYPE_FILE        /* Task stack info */
};

static const struct proc_node_s g_group =
{
  "group",        "group",   (uint8_t)PROC_GROUP,        DTYPE_DIRECTORY   /* Group directory */
};

static const struct proc_node_s g_groupstatus =
{
  "group/status", "status",  (uint8_t)PROC_GROUP_STATUS, DTYPE_FILE        /* Task group status */
};

static const struct proc_node_s g_groupfd =
{
  "group/fd",     "fd",      (uint8_t)PROC_GROUP_FD,     DTYPE_FILE        /* Group file descriptors */
};

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static const struct proc_node_s g_groupenv =
{
  "group/env",    "env",     (uint8_t)PROC_GROUP_ENV,    DTYPE_FILE        /* Group environment variables */
};

#endif

/* This is the list of all nodes */

static FAR const struct proc_node_s * const g_nodeinfo[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifndef CONFIG_SCHED_CPULOAD_NONE
  &g_loadavg,      /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  &g_critmon,      /* Critical section Monitor */
#endif
#if CONFIG_MM_BACKTRACE >= 0
  &g_heap,         /* Task heap info */
#endif
#ifdef CONFIG_DEBUG_MM
  &g_heapcheck,    /* Task heap check flag */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , &g_groupenv    /* Group environment variables */
#endif
};

#define PROC_NNODES (sizeof(g_nodeinfo)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all level0 nodes */

static const struct proc_node_s * const g_level0info[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifndef CONFIG_SCHED_CPULOAD_NONE
  &g_loadavg,      /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  &g_critmon,      /* Critical section monitor */
#endif
#if CONFIG_MM_BACKTRACE >= 0
  &g_heap,         /* Task heap info */
#endif
#ifdef CONFIG_DEBUG_MM
  &g_heapcheck,    /* Task heap check flag */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
};
#define PROC_NLEVEL0NODES (sizeof(g_level0info)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all group sub-directory nodes */

static FAR const struct proc_node_s * const g_groupinfo[] =
{
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , &g_groupenv    /* Group environment variables */
#endif
};
#define PROC_NGROUPNODES (sizeof(g_groupinfo)/sizeof(FAR const struct proc_node_s * const))

static FAR const char * const g_ttypenames[4] =
{
  "Task",
  "pthread",
  "Kthread",
  "Invalid"
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: proc_findnode
 ****************************************************************************/

static FAR const struct proc_node_s *proc_findnode(FAR const char *relpath)
{
  int i;

  /* Search every string in g_nodeinfo or until a match is found */

  for (i = 0; i < PROC_NNODES; i++)
    {
      size_t len = strlen(g_nodeinfo[i]->relpath);

      if (strncmp(g_nodeinfo[i]->relpath, relpath, len) == 0 &&
          (relpath[len] == '\0' || (relpath[len] == '/' &&
           relpath[len + 1] == '\0' &&
           g_nodeinfo[i]->dtype == DTYPE_DIRECTORY)))
        {
          return g_nodeinfo[i];
        }
    }

  /* Not found */

  return NULL;
}

/****************************************************************************
 * Name: proc_status
 *
 * Description:
 *   Format:
 *
 *            111111111122222222223
 *   123456789012345678901234567890
 *   Name:       xxxx...            Task/thread name (See
 *                                  CONFIG_TASK_NAME_SIZE)
 *   Type:       xxxxxxx            {Task, pthread, Kthread, Invalid}
 *   PPID:       xxxxx              Parent thread ID
 *   Group:      xxxxx              Group ID
 *   CPU:        xxx                CPU (CONFIG_SMP only)
 *   State:      xxxxxxxx,xxxxxxxxx {Invalid, Waiting, Ready, Running,
 *                                   Inactive},
 *                                  {Unlock, Semaphore, Signal, MQ empty,
 *                                   MQ full}
 *   Flags:      xxx                N,P,X
 *   Priority:   nnn                Decimal, 0-255
 *   Scheduler:  xxxxxxxxxxxxxx     {SCHED_FIFO, SCHED_RR, SCHED_SPORADIC}
 *   Sigmask:    nnnnnnnn           Hexadecimal, 32-bit
 *
 ****************************************************************************/

static ssize_t proc_status(FAR struct proc_file_s *procfile,
                           FAR struct tcb_s *tcb, FAR char *buffer,
                           size_t buflen, off_t offset)
{
  FAR const char *policy;
  FAR const char *name;
  char state[32];
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

  name       = get_task_name(tcb);
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%.18s\n", "Name:", name);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread type */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                             "Type:",
                             g_ttypenames[(tcb->flags & TCB_FLAG_TTYPE_MASK)
                             >>  TCB_FLAG_TTYPE_SHIFT]);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%d\n", "Group:",
                               tcb->group ? tcb->group->tg_pid : -1);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

#ifdef CONFIG_SMP
  if (tcb->task_state >= FIRST_ASSIGNED_STATE &&
      tcb->task_state <= LAST_ASSIGNED_STATE)
    {
      linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                                   "%-12s%d\n", "CPU:", tcb->cpu);
    }
  else
    {
      linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                                   "%-12s---\n", "CPU:");
    }

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

  /* Show the thread state */

  nxsched_get_stateinfo(tcb, state, sizeof(state));
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%s\n", "State:", state);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show task flags */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                          "%-12s%c\n", "Flags:",
                          tcb->flags & TCB_FLAG_EXIT_PROCESSING ? 'P' : '-');

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread priority */

#ifdef CONFIG_PRIORITY_INHERITANCE
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%d (%d)\n", "Priority:",
                               tcb->sched_priority, tcb->base_priority);
#else
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                               "Priority:", tcb->sched_priority);
#endif
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the scheduler policy */

  policy     = g_policy[(tcb->flags & TCB_FLAG_POLICY_MASK) >>
                        TCB_FLAG_POLICY_SHIFT];
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                               "Scheduler:", policy);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the signal mask. Note: sigset_t is uint32_t on NuttX. */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                             "%-12s" SIGSET_FMT "\n",
                             "SigMask:", SIGSET_ELEM(&tcb->sigprocmask));
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
  return totalsize;
}

/****************************************************************************
 * Name: proc_cmdline
 ****************************************************************************/

static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR const char *name;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

  name       = get_task_name(tcb);
  linesize   = strlen(name);
  memcpy(procfile->line, name, linesize);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the task / thread argument list (skipping over the name) */

  linesize   = nxtask_argvstr(tcb, procfile->line, sizeof(procfile->line));
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             sizeof(procfile->line), &offset);
  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  return totalsize;
}

/****************************************************************************
 * Name: proc_loadavg
 ****************************************************************************/

#ifndef CONFIG_SCHED_CPULOAD_NONE
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  struct cpuload_s cpuload;
  uint32_t intpart;
  uint32_t fracpart;
  size_t linesize;
  size_t copysize;

  /* Sample the counts for the thread.  clock_cpuload should only fail if
   * the PID is not valid.  This could happen if the thread exited sometime
   * after the procfs entry was opened.
   */

  clock_cpuload(procfile->pid, &cpuload);

  /* On the simulator, you may hit cpuload.total == 0, but probably never on
   * real hardware.
   */

  if (cpuload.total > 0)
    {
      uint32_t tmp;

      tmp      = (1000 * cpuload.active) / cpuload.total;
      intpart  = tmp / 10;
      fracpart = tmp - 10 * intpart;
    }
  else
    {
      intpart  = 0;
      fracpart = 0;
    }

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                             "%3" PRId32 ".%01" PRId32 "%%\n",
                             intpart, fracpart);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen,
                           &offset);

  return copysize;
}
#endif

/****************************************************************************
 * Name: proc_critmon
 ****************************************************************************/

#ifdef CONFIG_SCHED_CRITMONITOR
static ssize_t proc_critmon(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  struct timespec maxtime;
  struct timespec runtime;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Convert the for maximum time pre-emption disabled */

#if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0
  if (tcb->preemp_max > 0)
    {
      perf_convert(tcb->preemp_max, &maxtime);
    }
  else
    {
      maxtime.tv_sec = 0;
      maxtime.tv_nsec = 0;
    }

  /* Reset the maximum */

  tcb->preemp_max = 0;

  /* Generate output for maximum time pre-emption disabled */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu %p,",
                             (unsigned long)maxtime.tv_sec,
                             (unsigned long)maxtime.tv_nsec,
                             tcb->preemp_max_caller);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPT >= 0 */

  /* Convert and generate output for maximum time in a critical section */

#if CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0
  if (tcb->crit_max > 0)
    {
      perf_convert(tcb->crit_max, &maxtime);
    }
  else
    {
      maxtime.tv_sec = 0;
      maxtime.tv_nsec = 0;
    }

  /* Reset the maximum */

  tcb->crit_max = 0;

  /* Generate output for maximum time in a critical section */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu %p,",
                             (unsigned long)maxtime.tv_sec,
                             (unsigned long)maxtime.tv_nsec,
                             tcb->crit_max_caller);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0 */

  /* Convert and generate output for maximum time thread running */
#if CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD >= 0
  if (tcb->run_max > 0)
    {
      perf_convert(tcb->run_max, &maxtime);
    }
  else
    {
      maxtime.tv_sec = 0;
      maxtime.tv_nsec = 0;
    }

  /* Reset the maximum */

  tcb->run_max = 0;
  perf_convert(tcb->run_time, &runtime);

  /* Output the maximum time the thread has run and
   * the total time the thread has run
   */

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                             "%lu.%09lu,%lu.%09lu\n",
                             (unsigned long)maxtime.tv_sec,
                             (unsigned long)maxtime.tv_nsec,
                             (unsigned long)runtime.tv_sec,
                             (unsigned long)(runtime.tv_nsec));
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
#endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD >= 0 */

  return totalsize;
}
#endif

/****************************************************************************
 * Name: proc_heap
 ****************************************************************************/

#if CONFIG_MM_BACKTRACE >= 0
static ssize_t proc_heap(FAR struct proc_file_s *procfile,
                         FAR struct tcb_s *tcb, FAR char *buffer,
                         size_t buflen, off_t offset)
{
  size_t remaining = buflen;
  size_t linesize;
  size_t copysize;
  size_t totalsize = 0;
  struct mallinfo_task info;
  struct malltask task;

  task.pid = tcb->pid;
  task.seqmin = 0;
  task.seqmax = ULONG_MAX;
#ifdef CONFIG_MM_KERNEL_HEAP
  if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_KERNEL)
    {
      info = kmm_mallinfo_task(&task);
    }
  else
#endif
    {
      info = mallinfo_task(&task);
    }

  /* Show the heap alloc size */

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%d\n", "AllocSize:", info.uordblks);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the heap alloc block */

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s%d\n", "AllocBlks:", info.aordblks);
  totalsize += procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);
  return totalsize;
}
#endif

#ifdef CONFIG_DEBUG_MM
static ssize_t proc_heapcheck(FAR struct proc_file_s *procfile,
                              FAR struct tcb_s *tcb, FAR char *buffer,
                              size_t buflen, off_t offset)
{
  size_t remaining = buflen;
  size_t linesize;
  size_t copysize;
  size_t totalsize = 0;
  size_t heapcheck = 0;

  if (tcb->flags & TCB_FLAG_HEAP_CHECK)
    {
      heapcheck = 1;
    }

  linesize = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%zu\n",
                             "HeapCheck:", heapcheck);

  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);
  totalsize += copysize;
  return totalsize;
}

static ssize_t proc_heapcheck_write(FAR struct proc_file_s *procfile,
                                    FAR struct tcb_s *tcb,
                                    FAR const char *buffer,
                                    size_t buflen, off_t offset)
{
  switch (atoi(buffer))
    {
      case 0:
        tcb->flags &= ~TCB_FLAG_HEAP_CHECK;
        break;
      case 1:
        tcb->flags |= TCB_FLAG_HEAP_CHECK;
        break;
      default:
        ferr("ERROR: invalid argument\n");
        return -EINVAL;
        break;
    }

  return buflen;
}
#endif

/****************************************************************************
 * Name: proc_stack
 ****************************************************************************/

static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                          FAR struct tcb_s *tcb, FAR char *buffer,
                          size_t buflen, off_t offset)
{
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
#if CONFIG_SCHED_STACK_RECORD > 0
  int i;
#endif

  remaining = buflen;
  totalsize = 0;

  /* Show the stack alloc address */

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%p\n",
                               "StackAlloc:", tcb->stack_alloc_ptr);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack base address */

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%p\n",
                               "StackBase:", tcb->stack_base_ptr);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld\n",
                               "StackSize:", (long)tcb->adj_stack_size);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef CONFIG_STACK_COLORATION
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize = procfs_snprintf(
      procfile->line, STATUS_LINELEN, "%-12s%zu\n",
      "StackUsed:", up_check_tcbstack(tcb, tcb->adj_stack_size));

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

#if CONFIG_SCHED_STACK_RECORD > 0
  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%zu\n",
                              "StackMax: ",
                              tcb->stack_base_ptr +
                              tcb->adj_stack_size - tcb->sp_deepest);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%-s\n",
                               "Size", "Backtrace");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  for (i = tcb->level_deepest - 1; i >= 0; i--)
    {
      linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                                 "%-12zu%-pS\n",
                                 (i ? tcb->stackrecord_sp_deepest[i - 1] :
                                  tcb->stack_base_ptr + tcb->adj_stack_size)
                                 - tcb->stackrecord_sp_deepest[i],
                                 tcb->stackrecord_pc_deepest[i]);
      copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                               &offset);
      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;
    }

  if (tcb->caller_deepest)
    {
      linesize = procfs_snprintf(procfile->line, STATUS_LINELEN,
                                 "Warning stack record buffer too small\n"
                                 "Max: %u Overflow: %zu\n",
                                 CONFIG_SCHED_STACK_RECORD,
                                 tcb->caller_deepest);
      copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                               &offset);
      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;
    }
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupstatus
 ****************************************************************************/

static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                                FAR struct tcb_s *tcb, FAR char *buffer,
                                size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
#ifdef HAVE_GROUP_MEMBERS
  FAR sq_entry_t *curr;
  FAR sq_entry_t *next;
#endif

  DEBUGASSERT(group != NULL);

  remaining = buflen;
  totalsize = 0;

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                               "Main task:", group->tg_pid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                               "Parent:", group->tg_ppid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "%-12s0x%02x\n",
                               "Flags:", group->tg_flags);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef HAVE_GROUP_MEMBERS
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "%-12s%zu\n",
                               "Members:", sq_count(&group->tg_members));
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "Member IDs:");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  sq_for_every_safe(&group->tg_members, curr, next)
    {
      tcb = container_of(curr, struct tcb_s, member);
      linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, " %d",
                                   tcb->pid);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                 remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          return totalsize;
        }
    }

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupfd
 ****************************************************************************/

static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  FAR char *path;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
  int count;
  int i;

  DEBUGASSERT(group != NULL);

  count = fdlist_count(&group->tg_fdlist);
  if (count == 0)
    {
      return 0;
    }

  remaining = buflen;
  totalsize = 0;

  linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                               "\n%-3s %-7s %-4s %-9s %-14s %s\n",
                               "FD", "OFLAGS", "TYPE", "POS", "PATH",
#if CONFIG_FS_BACKTRACE > 0
                               "BACKTRACE"
#else
                               ""
#endif
                               );
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  path = lib_get_pathbuffer();
  if (path == NULL)
    {
      return totalsize;
    }

  /* Examine each open file descriptor */

  for (i = 0; i < count; i++)
    {
      FAR struct file *filep;
      FAR struct fd *fdp;

      /* Is there an inode associated with the file descriptor? */

      if (fdlist_get2(&group->tg_fdlist, i, &filep, &fdp) < 0)
        {
          continue;
        }

      if (file_ioctl(filep, FIOC_FILEPATH, path) < 0)
        {
          path[0] = '\0';
        }

      linesize   = procfs_snprintf(procfile->line, STATUS_LINELEN,
                                   "%-3d %-7d %-4x %-9ld %-14s ",
                                   i, filep->f_oflags,
                                   INODE_GET_TYPE(filep->f_inode),
                                   (long)filep->f_pos, path);
      if (linesize < STATUS_LINELEN)
        {
#if CONFIG_FS_BACKTRACE > 0
          linesize += backtrace_format(procfile->line + linesize,
                                       STATUS_LINELEN - linesize,
                                       fdp->f_backtrace,
                                       CONFIG_FS_BACKTRACE);
#endif
          procfile->line[linesize - 1] = '\n';
        }

      file_put(filep);
      copysize   = procfs_memcpy(procfile->line, linesize,
                                 buffer, remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          lib_put_pathbuffer(path);
          return totalsize;
        }
    }

  lib_put_pathbuffer(path);
  return totalsize;
}

/****************************************************************************
 * Name: proc_groupenv_callback
 ****************************************************************************/

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static int proc_groupenv_callback(FAR void *arg, FAR const char *pair)
{
  FAR struct proc_envinfo_s *info = (FAR struct proc_envinfo_s *)arg;
  FAR const char *src;
  FAR const char *value;
  FAR char *dest;
  char name[16 + 1];
  size_t linesize;
  size_t copysize;
  int namelen;

  DEBUGASSERT(arg != NULL && pair != NULL);

  /* Parse the name from the name/value pair */

  value  = NULL;
  namelen = 0;

  for (src = pair, dest = name; *src != '=' && *src != '\0'; src++)
    {
      if (namelen < 16)
        {
          *dest++ = *src;
          namelen++;
        }
    }

  /* NUL terminate the name string */

  *dest = '\0';

  /* Skip over the '=' to get the value */

  if (*src == '=')
    {
      value = src + 1;
    }
  else
    {
      value = "";
    }

  /* Output the header */

  linesize        = procfs_snprintf(info->procfile->line,
                                    STATUS_LINELEN, "%s=%s\n",
                                    name, value);
  copysize        = procfs_memcpy(info->procfile->line, linesize,
                                  info->buffer, info->remaining,
                                  &info->offset);

  info->totalsize += copysize;
  info->buffer    += copysize;
  info->remaining -= copysize;

  if (info->totalsize >= info->buflen)
    {
      return 1;
    }

  return 0;
}
#endif

/****************************************************************************
 * Name: proc_groupenv
 ****************************************************************************/

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static ssize_t proc_groupenv(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  struct proc_envinfo_s info;

  DEBUGASSERT(group != NULL);

  /* Initialize the info structure */

  info.procfile   = procfile;
  info.buffer     = buffer;
  info.offset     = offset;
  info.buflen     = buflen;
  info.remaining  = buflen;
  info.totalsize  = 0;

  /* Generate output for each environment variable */

  env_foreach(group, proc_groupenv_callback, &info);
  return info.totalsize;
}
#endif

/****************************************************************************
 * Name: proc_open
 ****************************************************************************/

static int proc_open(FAR struct file *filep, FAR const char *relpath,
                     int oflags, mode_t mode)
{
  FAR struct proc_file_s *procfile;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  FAR char *ptr;
  unsigned long tmp;
  pid_t pid;

  finfo("Open '%s'\n", relpath);

  /* The first segment of the relative path should be a task/thread ID or
   * the string "self".
   */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = nxsched_gettid();           /* Get the TID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL || *ptr != '/')
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* Skip over the slash */

  ptr++;

  /* A valid PID would be in the range of 0-INT_MAX (0 is reserved for the
   * IDLE thread).
   */

  if (tmp > INT_MAX)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is no longer valid\n", pid);
      return -ENOENT;
    }

  /* The remaining segments of the relpath should be a well known node in
   * the task/thread tree.
   */

  node = proc_findnode(ptr);
  if (node == NULL)
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* The node must be a file, not a directory */

  if (!DIRENT_ISFILE(node->dtype))
    {
      ferr("ERROR: Path \"%s\" is not a regular file\n", relpath);
      return -EISDIR;
    }

  /* Allocate a container to hold the task and node selection */

  procfile = (FAR struct proc_file_s *)
    fs_heap_zalloc(sizeof(struct proc_file_s));
  if (procfile == NULL)
    {
      ferr("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* Initialize the file container */

  procfile->pid  = pid;
  procfile->node = node;

  /* Save the index as the open-specific state in filep->f_priv */

  filep->f_priv = (FAR void *)procfile;
  return OK;
}

/****************************************************************************
 * Name: proc_close
 ****************************************************************************/

static int proc_close(FAR struct file *filep)
{
  FAR struct proc_file_s *procfile;

  /* Recover our private data from the struct file instance */

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile != NULL);

  /* Release the file container structure */

  fs_heap_free(procfile);
  filep->f_priv = NULL;
  return OK;
}

/****************************************************************************
 * Name: proc_read
 ****************************************************************************/

static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                         size_t buflen)
{
  FAR struct proc_file_s *procfile;
  FAR struct tcb_s *tcb;
  irqstate_t flags;
  ssize_t ret;

  finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);

  /* Recover our private data from the struct file instance */

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile != NULL);

  /* Verify that the thread is still valid */

  flags = enter_critical_section();
  tcb = nxsched_get_tcb(procfile->pid);
  if (tcb == NULL)
    {
      leave_critical_section(flags);
      ferr("ERROR: PID %d is not valid\n", procfile->pid);
      return -ENODEV;
    }

  /* Provide the requested data */

  switch (procfile->node->node)
    {
    case PROC_STATUS: /* Task/thread status */
      ret = proc_status(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_CMDLINE: /* Task command line */
      ret = proc_cmdline(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

#ifndef CONFIG_SCHED_CPULOAD_NONE
    case PROC_LOADAVG: /* Average CPU utilization */
      ret = proc_loadavg(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
    case PROC_CRITMON: /* Critical section monitor */
      ret = proc_critmon(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
#if CONFIG_MM_BACKTRACE >= 0
    case PROC_HEAP: /* Task heap info */
      ret = proc_heap(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
#ifdef CONFIG_DEBUG_MM
    case PROC_HEAP_CHECK: /* Task heap check flag */
      ret = proc_heapcheck(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
    case PROC_STACK: /* Task stack info */
      ret = proc_stack(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_STATUS: /* Task group status */
      ret = proc_groupstatus(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_FD: /* Group file descriptors */
      ret = proc_groupfd(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
    case PROC_GROUP_ENV: /* Group environment variables */
      ret = proc_groupenv(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif

     default:
      ret = -EINVAL;
      break;
    }

  leave_critical_section(flags);

  /* Update the file offset */

  if (ret > 0)
    {
      filep->f_pos += ret;
    }

  return ret;
}

/****************************************************************************
 * Name: proc_write
 ****************************************************************************/

static ssize_t proc_write(FAR struct file *filep, FAR const char *buffer,
                          size_t buflen)
{
  FAR struct proc_file_s *procfile;
  FAR struct tcb_s *tcb;
  ssize_t ret;

  DEBUGASSERT(buffer != NULL && buflen > 0);

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile != NULL);

  /* Verify that the thread is still valid */

  tcb = nxsched_get_tcb(procfile->pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is not valid\n", procfile->pid);
      return -ENODEV;
    }

  /* Provide the requested data */

  switch (procfile->node->node)
    {
#ifdef CONFIG_DEBUG_MM
      case PROC_HEAP_CHECK:
        ret = proc_heapcheck_write(procfile, tcb, buffer, buflen,
                                   filep->f_pos);
        break;
#endif

      default:
        ret = -EINVAL;
        break;
    }

  return ret;
}

/****************************************************************************
 * Name: proc_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/

static int proc_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct proc_file_s *oldfile;
  FAR struct proc_file_s *newfile;

  finfo("Dup %p->%p\n", oldp, newp);

  /* Recover our private data from the old struct file instance */

  oldfile = (FAR struct proc_file_s *)oldp->f_priv;
  DEBUGASSERT(oldfile != NULL);

  /* Allocate a new container to hold the task and node selection */

  newfile = fs_heap_malloc(sizeof(struct proc_file_s));
  if (newfile == NULL)
    {
      ferr("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* The copy the file information from the old container to the new */

  memcpy(newfile, oldfile, sizeof(struct proc_file_s));

  /* Save the new container in the new file structure */

  newp->f_priv = (FAR void *)newfile;
  return OK;
}

/****************************************************************************
 * Name: proc_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/

static int proc_opendir(FAR const char *relpath,
                        FAR struct fs_dirent_s **dir)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  unsigned long tmp;
  FAR char *ptr;
  pid_t pid;

  finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
  DEBUGASSERT(relpath != NULL);

  /* The relative must be either:
   *
   *  (1) "<pid>" - The sub-directory of task/thread attributes,
   *  (2) "self"  - Which refers to the PID of the calling task, or
   *  (3) The name of a directory node under either of those
   */

  /* Otherwise, the relative path should be a valid task/thread ID */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = nxsched_gettid();           /* Get the TID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL || (*ptr != '\0' && *ptr != '/'))
    {
      /* strtoul failed or there is something in the path after the pid */

      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* A valid PID would be in the range of 0-INT_MAX (0 is reserved for the
   * IDLE thread).
   */

  if (tmp > INT_MAX)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is not valid\n", pid);
      return -ENOENT;
    }

  /* Allocate the directory structure.  Note that the index and procentry
   * pointer are implicitly nullified by fs_heap_zalloc().
   * Only the remaining, non-zero entries will need be initialized.
   */

  procdir = fs_heap_zalloc(sizeof(struct proc_dir_s));
  if (procdir == NULL)
    {
      ferr("ERROR: Failed to allocate the directory structure\n");
      return -ENOMEM;
    }

  /* Was the <pid> the final element of the path? */

  if (*ptr != '\0' && strcmp(ptr, "/") != 0)
    {
      /* There is something in the path after the pid.  Skip over the path
       * segment delimiter and see if we can identify the node of interest.
       */

      ptr++;
      node = proc_findnode(ptr);
      if (node == NULL)
        {
          ferr("ERROR: Invalid path \"%s\"\n", relpath);
          fs_heap_free(procdir);
          return -ENOENT;
        }

      /* The node must be a directory, not a file */

      if (!DIRENT_ISDIRECTORY(node->dtype))
        {
          ferr("ERROR: Path \"%s\" is not a directory\n", relpath);
          fs_heap_free(procdir);
          return -ENOTDIR;
        }

      /* This is a second level directory */

      procdir->base.level    = 2;
      procdir->base.nentries = PROC_NGROUPNODES;
      procdir->node          = node;
    }
  else
    {
      /* Use the special level0 node */

      procdir->base.level    = 1;
      procdir->base.nentries = PROC_NLEVEL0NODES;
      procdir->node          = &g_level0node;
    }

  procdir->pid  = pid;
  *dir = (FAR struct fs_dirent_s *)procdir;
  return OK;
}

/****************************************************************************
 * Name: proc_closedir
 *
 * Description: Close the directory listing
 *
 ****************************************************************************/

static int proc_closedir(FAR struct fs_dirent_s *dir)
{
  DEBUGASSERT(dir != NULL);
  fs_heap_free(dir);
  return OK;
}

/****************************************************************************
 * Name: proc_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int proc_readdir(FAR struct fs_dirent_s *dir,
                        FAR struct dirent *entry)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node = NULL;
  FAR struct tcb_s *tcb;
  unsigned int index;
  pid_t pid;
  int ret;

  DEBUGASSERT(dir != NULL);
  procdir = (FAR struct proc_dir_s *)dir;

  /* Have we reached the end of the directory */

  index = procdir->base.index;
  if (index >= procdir->base.nentries)
    {
      /* We signal the end of the directory by returning the special
       * error -ENOENT
       */

      finfo("Entry %d: End of directory\n", index);
      ret = -ENOENT;
    }

  /* No, we are not at the end of the directory */

  else
    {
      /* Verify that the pid still refers to an active task/thread */

      pid = procdir->pid;

      tcb = nxsched_get_tcb(pid);
      if (tcb == NULL)
        {
          ferr("ERROR: PID %d is no longer valid\n", pid);
          return -ENOENT;
        }

      /* The TCB is still valid (or at least was when we entered this
       * function)
       *
       * Handle the directory listing by the node type.
       */

      switch (procdir->node->node)
        {
         case PROC_LEVEL0: /* Top level directory */
           DEBUGASSERT(procdir->base.level == 1);
           node = g_level0info[index];
           break;

         case PROC_GROUP:  /* Group sub-directory */
           DEBUGASSERT(procdir->base.level == 2);
           node = g_groupinfo[index];
           break;

         default:
           return -ENOENT;
        }

      /* Save the filename and file type */

      entry->d_type = node->dtype;
      strlcpy(entry->d_name, node->name, sizeof(entry->d_name));

      /* Set up the next directory entry offset.  NOTE that we could use the
       * standard f_pos instead of our own private index.
       */

      procdir->base.index = index + 1;
      ret = OK;
    }

  return ret;
}

/****************************************************************************
 * Name: proc_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int proc_rewinddir(struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *priv;

  DEBUGASSERT(dir != NULL);
  priv = (FAR struct proc_dir_s *)dir;

  priv->base.index = 0;
  return OK;
}

/****************************************************************************
 * Name: proc_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int proc_stat(const char *relpath, struct stat *buf)
{
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  unsigned long tmp;
  FAR char *ptr;
  pid_t pid;

  /* Two path forms are accepted:
   *
   * "<pid>" - If <pid> refers to a currently active task/thread, then it
   *   is a directory
   * "<pid>/<node>" - If <node> is a recognized node then, then it
   *   is a file or directory.
   */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = nxsched_gettid();           /* Get the TID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL)
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* A valid PID would be in the range of 0-INT_MAX (0 is reserved for the
   * IDLE thread).
   */

  if (tmp > INT_MAX)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is no longer valid\n", pid);
      return -ENOENT;
    }

  /* Was the <pid> the final element of the path? */

  memset(buf, 0, sizeof(struct stat));
  if (*ptr == '\0' || strcmp(ptr, "/") == 0)
    {
      /* Yes ... It's a read-only directory */

      buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
    }

  /* Verify that the process ID is followed by valid path segment delimiter */

  else if (*ptr != '/')
    {
      /* We are required to return -ENOENT all all invalid paths */

      ferr("ERROR: Bad delimiter '%c' in relpath '%s'\n", *ptr, relpath);
      return -ENOENT;
    }
  else
    {
      /* Otherwise, the second segment of the relpath should be a well
       * known node of the task/thread directory structure.
       */

      /* Skip over the path segment delimiter */

      ptr++;

      /* Lookup the well-known node associated with the relative path. */

      node = proc_findnode(ptr);
      if (node == NULL)
        {
          ferr("ERROR: Invalid path \"%s\"\n", relpath);
          return -ENOENT;
        }

      /* If the node exists, it is the name for a read-only file or
       * directory.
       */

      if (node->dtype == DTYPE_FILE)
        {
          buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
        }
      else
        {
          buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
        }
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */
